-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
--
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.
local log = require 'mc.logging'
local context = require 'mc.context'
local cjson = require 'cjson'
local utils = require 'mc.utils'
local file_utils = require 'utils.file'
local core_utils = require 'utils.core'
local mdb_service = require 'mc.mdb.mdb_service'

local m = {}

local PATH_ACCOUNT_CERT<const> = "/bmc/kepler/AccountService/MultiFactorAuth/ClientCertificate/Certificates/%d"
local PRE_SUFFIX_SYSTEM = '/bmc/kepler/Systems/'
local PRE_SUFFIX_SYSTEM_LEN = string.len('/bmc/kepler/Systems/')

function m.is_valid_entries_id(entries_id, count)
    -- 判断字符串中的每一个字符是否为数字
    if not string.match(entries_id, '^([0-9]+)$') then
        return false
    end

    if tonumber(entries_id) <= 0 or tonumber(entries_id) > count then
        log:error('log_service entries_id %s is invalid.', entries_id)
        return false
    end

    return true
end

function m.is_valid_skip_and_top(count, in_skip, in_top)
    local skip = tonumber(in_skip)
    local top = tonumber(in_top)

    if skip >= count or skip < 0 then
        error(base_messages.QueryParameterOutOfRange(skip, '$skip', '0-'.. (count - 1)))
    elseif skip + top > count or top <= 0 then
        error(base_messages.QueryParameterOutOfRange(top, '$top', '1-' .. (count - skip)))
    end

    return true
end

function m.get_next_link(managerid, logtype, count, in_skip, in_top)
    local MAX_TOP_NUM = 32

    local skip = in_skip or 0
    local top = in_top or (count > 32 and 32 or count)

    if tonumber(top) > 32 then
        top = 32
    end

    if count >= (skip + top + MAX_TOP_NUM) then
        return '/redfish/v1/Managers/' .. managerid .. '/LogServices/' .. logtype .. '/Entries?$skip=' ..
                   (skip + top) .. '&$top=32'
    elseif count > (skip + top) then
        return '/redfish/v1/Managers/' .. managerid .. '/LogServices/' .. logtype .. '/Entries?$skip=' ..
                   (skip + top) .. '&$top=' .. (count - skip - top)
    else
        return
    end
end

-- 判断删除远程HTTPS传输服务器根证书的吊销列表方法路径是否存在
function m.is_valid_remote_https_server_crl_path(path)
    local ok = pcall(mdb.get_object, bus, path, 'bmc.kepler.CertificateService.Certificate')
    if not ok then
        error(custom_messages.RootCANotExists())
    end
    return true
end

-- 获取证书的key_usage扩展字段
local function get_key_usage(key_usage_str)
    local res = {}
    local delimiter = ', '
    -- 使用 Lua 的 string.gmatch 函数进行字符串的拆分
    for match in string.gmatch(key_usage_str, "([^" .. delimiter .. "]+)") do
        res[#res + 1] = match
    end
    local key_usage_table = {
        DigitalSignature = 'Digital Signature',
        NonRepudiation = 'Non Repudiation',
        KeyEncipherment = 'Key Encipherment',
        DataEncipherment = 'Data Encipherment',
        KeyAgreement = 'Key Agreement',
        KeyCertSign = 'Certificate Sign',
        CRLSigning = 'CRL Sign',
        EncipherOnly = 'Encipher Only',
        DecipherOnly = 'Decipher Only'
    }
    local rsp = ''
    for i = 1, #res do
        if i ~= #res then
            rsp = rsp .. key_usage_table[res[i]] .. ', '
        else
            rsp = rsp .. key_usage_table[res[i]]
        end
    end
    return rsp
end

---@function 创建返回证书信息json对象
---@param    is_server_cert boolean
---@param    cert_name_type 0:Issuer、Subject、ValidNotBefore、ValidNotAfter 1：IssueBy、IssueTo、ValidFrom、ValidTo
local function create_cert_object(is_server_cert, cert_name_type, rsp)
    local cert = cjson.json_object_new_object()

    if cert_name_type == 1 then
        cert.IssueBy = rsp.ServerCert.Issuer
        cert.IssueTo = rsp.ServerCert.Subject
        cert.ValidFrom = rsp.ServerCert.ValidNotBefore
        cert.ValidTo = rsp.ServerCert.ValidNotAfter
    else
        cert.Issuer = rsp.ServerCert.Issuer
        cert.Subject = rsp.ServerCert.Subject
        cert.ValidNotBefore = rsp.ServerCert.ValidNotBefore
        cert.ValidNotAfter = rsp.ServerCert.ValidNotAfter
    end

    cert.SerialNumber = rsp.ServerCert.SerialNumber
    cert.SignatureAlgorithm = rsp.ServerCert.FingerprintHashAlgorithm
    cert.KeyUsage = get_key_usage(rsp.ServerCert.KeyUsage)
    cert.PublicKeyLengthBits = rsp.ServerCert.KeyLength
    cert.FingerPrint = rsp.ServerCert.Fingerprint
    cert.IsImportCrl = rsp.ServerCert.IsImportCrl or false        -- 证书链设置为false
    cert.CrlValidFrom = rsp.ServerCert.CrlValidFrom or cjson.null -- 证书链设置为null
    cert.CrlValidTo = rsp.ServerCert.CrlValidTo or cjson.null     -- 证书链设置为null

    if is_server_cert then
        local cert_server = cjson.json_object_new_object()
        cert_server.ServerCert = cert
        return cert_server
    else
        return cert
    end
end

---@function 获取客户端证书的信息
---@param    cert_id number
---@return   table
local function get_client_cert_info(cert_id, rsp)
    local client_cert_info = cjson.json_object_new_object()
    client_cert_info.CertId = cert_id
    client_cert_info.IssueBy = rsp.ServerCert.IssueBy or cjson.null
    client_cert_info.IssueTo = rsp.ServerCert.IssueTo or cjson.null
    client_cert_info.ValidFrom = rsp.ServerCert.ValidNotBefore or cjson.null
    client_cert_info.ValidTo = rsp.ServerCert.ValidNotAfter or cjson.null
    client_cert_info.SerialNumber = rsp.ServerCert.SerialNumber or cjson.null
    client_cert_info.SignatureAlgorithm = rsp.ServerCert.FingerprintHashAlgorithm or cjson.null
    client_cert_info.KeyUsage = get_key_usage(rsp.ServerCert.KeyUsage) or cjson.null
    client_cert_info.PublicKeyLengthBits = rsp.ServerCert.KeyLength or cjson.null
    client_cert_info.FingerPrint = rsp.ServerCert.Fingerprint or cjson.null
    client_cert_info.IsImportCrl = rsp.ServerCert.IsImportCrl or false      -- 证书链设置为false
    client_cert_info.RevokedState = rsp.ServerCert.RevokedState or false
    client_cert_info.RevokedDate = rsp.ServerCert.RevokedDate or cjson.null -- 证书链设置为null
    client_cert_info.RootCertUploadedState = rsp.ServerCert.RootCertUploadedState
    return client_cert_info
end

-- 1查询安全服务集合资源信息接口的RemoteHttpsServerCertChainInfo字段数据获取
-- 2查询具体域控制器的信息CertificateChainInformation字段
---@param    cert_name_type 0:Issuer、Subject、ValidNotBefore、ValidNotAfter 1：IssueBy、IssueTo、ValidFrom、ValidTo
function m.get_cert_info(cert_list, cert_name_type, interface_id)
    local cert_id_arr = {}
    local chain_info = cjson.json_object_new_array() -- cjson有序接口，生成的JSON对象数据保持与添加的顺序一致
    local cert_service_obj = mdb.get_object(bus, '/bmc/kepler/CertificateService', 'bmc.kepler.CertificateService')
    for _, cert_path in ipairs(cert_list) do
        local cert_id = tonumber(string.match(cert_path, "%d+$")) -- 匹配以数字结尾的子串
        table.insert(cert_id_arr, cert_id)
    end
    table.sort(cert_id_arr)
    for _, cert_id in ipairs(cert_id_arr) do
        local rsp = cert_service_obj:GetCertChainInfo(context.new(), 0, cert_id)
        rsp = cjson.decode(rsp.CertInfo)
        local tmp_server_cert = create_cert_object(true, cert_name_type, rsp)
        tmp_server_cert.CertId = cert_id
        if interface_id == 1 then
            tmp_server_cert.Usage = 'Manager CA Certificate'
        end
        chain_info[#chain_info + 1] = tmp_server_cert
    end
    return chain_info
end

-- "ManagerCACertificate": 0, "ManagerSSLCertificate": 1, "ManagerAccountCertificate": 2
---@param    cert_name_type 0:Issuer、Subject、ValidNotBefore、ValidNotAfter 1：IssueBy、IssueTo、ValidFrom、ValidTo
function m.get_root_or_client_info(cert_usage_type, cert_name_type, cert_list)
    local cert_service_obj = mdb.get_object(bus, '/bmc/kepler/CertificateService', 'bmc.kepler.CertificateService')
    if cert_usage_type == 0 then
        local chain_info = cjson.json_object_new_array() -- cjson有序接口，生成的JSON对象数据保持与添加的顺序一致
        local cert_id_arr = {}
        for _, cert_path in ipairs(cert_list) do
            local cert_id = tonumber(string.match(cert_path, "%d+$")) -- 匹配以数字结尾的子串
            table.insert(cert_id_arr, cert_id)
        end
        table.sort(cert_id_arr)
        for _, cert_id in ipairs(cert_id_arr) do
            local rsp = cert_service_obj:GetCertChainInfo(context.new(), cert_usage_type, cert_id)
            rsp = cjson.decode(rsp.CertInfo)
            local tmp_root_cert = create_cert_object(true, cert_name_type, rsp)
            tmp_root_cert.CertId = cert_id
            chain_info[#chain_info + 1] = tmp_root_cert
        end
        return chain_info
    else
        local rsp = cert_service_obj:GetCertChainInfo(context.new(), cert_usage_type, 1) -- 只展示证书Id为1的
        rsp = cjson.decode(rsp.CertInfo)
        local tmp_server_cert = cjson.json_object_new_object()
        tmp_server_cert.Issuer = rsp.ServerCert.Issuer
        tmp_server_cert.Subject = rsp.ServerCert.Subject
        tmp_server_cert.ValidNotBefore = rsp.ServerCert.ValidNotBefore
        tmp_server_cert.ValidNotAfter = rsp.ServerCert.ValidNotAfter
        tmp_server_cert.SerialNumber = rsp.ServerCert.SerialNumber
        tmp_server_cert.SignatureAlgorithm = rsp.ServerCert.FingerprintHashAlgorithm
        tmp_server_cert.KeyUsage = get_key_usage(rsp.ServerCert.KeyUsage)
        tmp_server_cert.PublicKeyLengthBits = rsp.ServerCert.KeyLength
        return tmp_server_cert
    end
end

local function get_cert_info(cert_info, mdb_info)
    cert_info.Subject = mdb_info.Subject
    cert_info.Issuer = mdb_info.Issuer
    cert_info.ValidNotBefore = mdb_info.ValidNotBefore
    cert_info.ValidNotAfter = mdb_info.ValidNotAfter
    cert_info.SerialNumber = mdb_info.SerialNumber
    cert_info.SignatureAlgorithm = mdb_info.FingerprintHashAlgorithm
    cert_info.KeyUsage = get_key_usage(mdb_info.KeyUsage)
    cert_info.PublicKeyLengthBits = mdb_info.KeyLength
end

-- 查询SSL证书资源信息的X509CertificateInformation字段数据获取
function m.get_certificate_information(cert_list, id)
    local chain_info = cjson.json_object_new_object() -- cjson有序接口，生成的JSON对象数据保持与添加的顺序一致
    local cert_service_obj = mdb.get_object(bus, '/bmc/kepler/CertificateService', 'bmc.kepler.CertificateService')
    local cert_id = 0
    for _, cert_path in ipairs(cert_list) do
        if tonumber(string.match(cert_path, "%d+$")) == id then
            cert_id = id
        end
    end
    if cert_id == 0 then
        return chain_info
    end
    local rsp = cert_service_obj:GetCertChainInfo(context.new(), 1, cert_id) -- 1表示SSL证书
    rsp = cjson.decode(rsp.CertInfo)
    local tmp_server_cert = cjson.json_object_new_object()
    tmp_server_cert.ServerCert = cjson.json_object_new_object()

    get_cert_info(tmp_server_cert.ServerCert, rsp.ServerCert)
    if cert_id == 1 then
        tmp_server_cert.ServerCert.IsDefaultSSLCert = cert_service_obj.IsDefaultSSLCert
    end
    if type(rsp.IntermediateCert) == 'table' and #rsp.IntermediateCert >= 1 then
        tmp_server_cert.IntermediateCert = cjson.json_object_new_array()
        for _, intermediate_cert in pairs(rsp.IntermediateCert) do
            local tmp_cert_data = cjson.json_object_new_object()
            get_cert_info(tmp_cert_data, intermediate_cert)
            tmp_server_cert.IntermediateCert[#tmp_server_cert.IntermediateCert + 1] = tmp_cert_data
        end
    end
    if type(rsp.RootCert) == 'table' then
        tmp_server_cert.RootCert = cjson.json_object_new_object()
        get_cert_info(tmp_server_cert.RootCert, rsp.RootCert)
    end
    return tmp_server_cert
end

-- 查询Ldap域控制器集合信息UserDomain由Folder和UserDomain字段拼接显示于redfish
function m.get_user_domain(folder, user_domain)
    local rsp = folder .. ',DC='
    local delimiter = '.'
    local gmatch_res = {}
    -- 使用 Lua 的 string.gmatch 函数进行字符串的拆分
    for match in string.gmatch('.' .. user_domain, "([^" .. delimiter .. "]+)") do
        gmatch_res[#gmatch_res + 1] = match
    end
    rsp = rsp .. table.concat(gmatch_res, ',DC=')
    return rsp
end

-- 输入格式为CN=test,OU=testusers,DC=huawei,DC=com，最后返回为CN=test,OU=testuser和huawei.com
-- CN=test,OU=testusers,可能为空
-- ,DC=huawei,DC=com可能为空
function m.split_user_domain(user_domain)
    if string.len(user_domain) == 0 then
        return {nil, nil}
    end
    local delimiter = ','
    -- 使用 Lua 的 string.gmatch 函数进行字符串的拆分
    local gmatch_res = {}
    for match in string.gmatch(user_domain, "([^" .. delimiter .. "]+)") do
        gmatch_res[#gmatch_res + 1] = match
    end
    local rsp = {}
    rsp[1] = {}
    rsp[2] = {}
    for _, match in ipairs(gmatch_res) do
        if string.sub(match, 1, 3) ~= 'DC=' then
            table.insert(rsp[1], match)
        else
            table.insert(rsp[2], string.sub(match, 4)) -- 只需要下标4开始的字段，如上例拼成huawei.com
        end
    end
    if #rsp[1] == 0 then
        rsp[1] = ''
    else
        rsp[1] = table.concat(rsp[1], ',')
    end
    if #rsp[2] == 0 then
        rsp[2] = ''
    else
        rsp[2] = table.concat(rsp[2], '.')
    end
    -- DC都为空时，rsp[2]会被拼接成一个全是.的字符串，需要将该字符串置为空
    if rsp[2]:match("^%.+$") then
        rsp[2] = ''
    end
    return rsp
end

-- 修改具体域控制器的信息,GroupDomain存在时需要与GroupName与GroupFolder进行规则校验
function m.group_domain_check(g_name, g_folder, g_domain)
    if string.len(g_domain) == 0 then -- 该字段非必须
        return true
    end
    local g_name_folder = ''
    if string.len(g_name) ~= 0 then
        g_name_folder = g_name_folder .. 'CN=' .. g_name
    end
    if string.len(g_folder) ~= 0 then
        if string.len(g_name) ~= 0 then
            g_name_folder = g_name_folder .. ',OU=' .. g_folder
        else
            g_name_folder = g_name_folder .. 'OU=' .. g_folder
        end
    end
    local g_name_folder_len = string.len(g_name_folder)
    if string.sub(g_domain, 1, g_name_folder_len) ~= g_name_folder then
        error(base_messages.PropertyValueFormatError(g_domain, 'GroupDomain'))
    end
    return true
end

-- 删除语言
function m.remove_language(mdb_lang, rm_lang_str)
    local language_set_support = {
        ['en'] = true,
        ['zh'] = true,
        ['ja'] = true,
        ['fr'] = true,
        ['ru'] = true
    }

    local rm_langs = utils.split(rm_lang_str, ',') or {}
    if next(rm_langs) == nil then
        error(custom_messages.LanguageNotSupport(rm_lang_str))
    end

    -- 检查传入的是否合法
    for _, rm_lang in ipairs(rm_langs) do
        if not language_set_support[rm_lang] then
            error(custom_messages.LanguageNotSupport(rm_lang))
        end
        if not utils.array_contains(mdb_lang, rm_lang) then
            error(custom_messages.LanguageNotInstalled(rm_lang))
        end
    end

    local res = {}
    for _, lang in ipairs(mdb_lang) do
        if not utils.array_contains(rm_langs, lang) then
            res[#res + 1] = lang
        end
    end
    return res
end

function m.is_import_permitted(type, content, role_id, is_permitted)
    if type ~= 'URI' then
        return true
    end

    local uri_pattern = "^((https|sftp|nfs|cifs|scp)://.{1,1000}|/tmp/.{1,246})\\.json$"
    if not core_utils.g_regex_match(uri_pattern, content) then
        error(base_messages.PropertyValueFormatError("******", "Content"))
    end

    if content:sub(1,1) ~= '/' then
        return true
    end

    if not core_utils.is_file(content) then
        error(custom_messages.InvalidPath("******", "Content"))
    end

    if file_utils.check_real_path_s(content, "/tmp") ~= 0 then
        error(custom_messages.InvalidPath("******", "Content"))
    end

    if role_id == 4 or is_permitted then        -- 4 管理员RoleId
        return true
    end
    error(custom_messages.NoPrivilegeToOperateSpecifiedFile())
end

function m.get_client_cert_chain_info(cert_id)
    local cert_service_obj = mdb.get_object(bus, '/bmc/kepler/CertificateService', 'bmc.kepler.CertificateService')
    -- 由于事件上报会定期访问redfish用户接口，因此这里在获取信息前先判断客户端证书是否存在，避免刷屏
    local account_cert_id = tonumber(cert_id)
    local account_cert_path = string.format(PATH_ACCOUNT_CERT, account_cert_id)
    local ok, rsp = pcall(mdb_service.is_valid_path, bus, account_cert_path)
    if not ok then
        log:info('Invalid Path, err(%s)', rsp.message)
        error(rsp)
    end
    local is_exist = rsp.Result
    if not is_exist then
        return
    end
    rsp = cert_service_obj:GetCertChainInfo(context.new(), 2, account_cert_id)
    rsp = cjson.decode(rsp.CertInfo)
    return get_client_cert_info(account_cert_id, rsp)
end

function m.get_task_details(details)
    result = {}
    local scopes = {'BIOS', 'RAID', 'Disk', 'BMC'}
    for _, scope in ipairs(scopes) do
        if details[scope] then
            table.insert(result, {Scope = scope, State = details[scope]})
        end
    end
    return result
end

local Role = {
    ["IPMB"] = "IPMBAccessRole",
    ["SMS"] = "SMSAccessRole",
    ["ICMB"] = "ICMBAccessRole"
}
-- 获取ipmi通道权限
function m.get_ipmi_channel_access_role(access_role)
    local data = cjson.json_object_new_object()
    for key, value in pairs(access_role) do
        data[Role[key]] = value
    end
    return data
end

function m.get_servers()
    local ok, rsp = pcall(mdb_service.get_sub_paths, bus, '/bmc/kepler/Systems', 20, {})
    if not ok then
        log:error('Invalid path, err(%s)', rsp)
    end

    local result = {}
    local sysids = {}
    local hash = {}
    local sub_str
    local start
    if type(rsp.SubPaths) == 'table' then
        for _, v in pairs(rsp.SubPaths) do
            start = string.find(v, '/', PRE_SUFFIX_SYSTEM_LEN + 1)
            if start == nil then
                sub_str = v:sub(PRE_SUFFIX_SYSTEM_LEN + 1)
            elseif start > PRE_SUFFIX_SYSTEM_LEN + 1 then
                sub_str = v:sub(PRE_SUFFIX_SYSTEM_LEN + 1, start - 1)
            end
            if sub_str ~= nil and sub_str ~= '' and hash[sub_str] == nil then
                table.insert(sysids, {
                    sysid = tonumber(sub_str),
                    url = '/redfish/v1/Systems/' .. sub_str
                })
                hash[sub_str] = true
            end
        end
    end

    if next(sysids) then
        table.sort(sysids, function(a, b)
            return a.sysid < b.sysid
        end)
        for _, v in pairs(sysids) do
            result[#result + 1] = {
                ['@odata.id'] = v.url
            }
        end
    else
        result[#result + 1] = {
            ['@odata.id'] ='/redfish/v1/Systems/1'
        }
    end
    return result
end

return m
